﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Linq.Expressions;


namespace DDD.Core {

    /// <summary>
    /// <c>Uniform</c> 変数を保存するコンテナ クラス
    /// </summary>
    /// <remarks>
    /// <see cref="UniformArray"/> は <c>Uniform</c> 変数を保存するためのコンテナ クラスです。
    /// 複数の <c>Uniform</c> 変数を一括して管理することが可能です。
    /// シェーダーに送られる <c>Uniform</c> 変数は､必ずこの<see cref=" UniformArray"/> オブジェクト単位で追加します。
    /// 1つのアピアランスに複数の <see cref="UniformArray"/> オブジェクトをセット可能です｡
    /// 詳しくは､<see cref="Appearance"/> の解説を参照してください。
    /// </remarks>
    /// <seealso cref="Appearance"/>
    /// <seealso cref="UniformVariable"/>
    /// <seealso cref="ShaderDeclaration"/>
    public class UniformArray : Object, IEnumerable<UniformVariable> {

        #region Field
        List<UniformVariable> uniforms;

        /// <summary>
        /// シーンのアクティブ カメラを指す特殊ノード
        /// </summary>
        /// <remarks>
        /// このノードがソース オブジェクトに指定された場合､プロパティの参照元として常に最新の<see cref="World.ActiveCamera"/>を参照します。
        /// この特殊ノードを使用せず直接<see cref="World.ActiveCamera"/>を指定した場合はアクティブカメラを切り替えた時に
        /// 古い方のカメラを参照し続けます。
        /// </remarks>
        public static Camera CurrentActiveCamera = new Camera ();

        /// <summary>
        /// この <see cref="UniformArray"/> オブジェクトがアタッチされたノードを示す特殊ノード
        /// </summary>
        /// <remarks>
        /// <c>Uniform</c> 変数のソース オブジェクトとしてこの特殊ノードを指定すると､
        /// 特定のオブジェクトにではなく､この <see cref="UniformArray"/> オブジェクトがアタッチされたノードを参照します｡
        /// このノードは1つの <see cref="UniformArray"/> オブジェクトを複数のノードで共通で使用する時に､
        /// ソース オブジェクトとして各ノードを参照するのに使用します｡
        /// </remarks>
        /// <seealso cref="Node"/>
        public static Node CurrentNode = new Node();

        /// <summary>
        /// この <see cref="UniformArray"/> オブジェクトがアタッチされたマテリアルを示す特殊ノード
        /// </summary>
        /// <remarks>
        /// <c>Uniform</c> 変数のソース オブジェクトとしてこの特殊マテリアルを指定すると､
        /// 特定のオブジェクトではなくこの <see cref="UniformArray"/> オブジェクトがアタッチされたアピアランスの
        /// マテリアルを参照します｡
        /// このノードは1つの <see cref="UniformArray"/> オブジェクトを複数のノードで共通で使用する時に､
        /// ソース オブジェクトとして各ノードのマテリアルを参照するのに使用します｡
        /// </remarks>
        /// <seealso cref="Appearance"/>
        /// <seealso cref="Material"/>
        public static Material CurrentMaterial = new Material ();
        #endregion

        #region Constructor
        /// <summary>
        /// コンストラクター
        /// </summary>
        /// <remarks>
        /// 設定済みの<c>Uniform</c> 変数はありません｡
        /// </remarks>
        public UniformArray () {
            this.uniforms = new List<UniformVariable> ();
        }
        #endregion

        #region Property
        /// <summary>
        /// 現在このアレイにセットされている <c>Unfiorm</c> 変数の個数
        /// </summary>
        /// <remarks>
        /// 現在このアレイにセットされている <c>Unfiorm</c> 変数の個数
        /// </remarks>
        public int Count {
            get { return uniforms.Count; }
        }

        /// <summary>
        /// 指定の名前の <c>Uniform</c> 変数を取得するインデクサー
        /// </summary>
        /// <remarks>
        /// 指定の名前の<c>Uniform</c> 変数が登録されていない場合、例外が発生します｡
        /// このメソッドは <see cref="Get()"/> の別名です｡
        /// </remarks>
        /// <param name="name">検索する<c>Uniform</c> 変数名</param>
        /// <exception cref="ArgumentNullException"><paramref name="name"/> が <c>null</c> または空文字列</exception>
        /// <exception cref="ArgumentException"><paramref name="name"/> が見つからない</exception>
        /// <returns><see cref="UniformVariable"/> 構造体</returns>
        public UniformVariable this[string name] {
            get {
                return Get (name);
            }
        }

        /// <inheritdoc/>
        public override IEnumerable<Object> References {
            get {
                return base.References;
            }
        }
        #endregion

        #region Method
        /// <inheritdoc/>
        IEnumerator<UniformVariable> IEnumerable<UniformVariable>.GetEnumerator () {
            return ((IEnumerable<UniformVariable>)uniforms).GetEnumerator ();
        }

        /// <inheritdoc/>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () {
            return uniforms.GetEnumerator ();
        }

        /// <summary>
        /// <c>Uniform</c> 変数を登録します
        /// </summary>
        /// <remarks>
        /// シェーダーに <c>Uniform</c> 変数として送るオブジェクトとそのプロパティ名を登録します｡
        /// プロパティの値はこの時点では評価されず､<see cref="Node.Refresh()"/> が呼ばれるまで遅延されます(遅延評価)｡
        /// 既に登録されている名前と同名の <c>Uniform</c> 変数を登録した場合は､古い方は削除され新しいものに置き換わります。
        /// プロパティはシェーダーで宣言された <c>Uniform</c> 変数の型と一致するか､互換性のある型のものを指定する必要があります｡
        /// 詳しくは <see cref="UniformVariable"/> の解説を参照してください。
        /// <para>
        /// <h3>式木を使ったプロパティ名の指定の仕方について</h3>
        /// リファクタリングしやすいようにプロパティ名は文字列で指定するのではなく､
        /// 式木を使ってプロパティからプロパティ名をプログラム的に取得する形での実装となっています｡
        /// </para>
        /// <code title="式木を使ったプロパティ名の指定)language="C#" >
        ///   var arry = new UniformArray();
        ///   arry.Add ("Some uniform", src, () => src.PropertyName)
        /// </code>
        /// <note type="implement">
        /// 文字列で指定する<see cref="Add()"/> も提供すべきか?
        /// 原則同じ機能を持つ複数のメソッドは提供していないが､すでに例外がある事だし提供してもいい気がする｡
        /// </note>
        /// </remarks>
        /// <typeparam name="TObj">ソース オブジェクトの型</typeparam>
        /// <typeparam name="T">プロパティの型</typeparam>
        /// <param name="name"><c>Uniform</c> 変数名</param>
        /// <param name="src">オブジェクト ソース</param>
        /// <param name="e">プロパティを表す式木</param>
        /// <exception cref="ArgumentNullException"><paramref name="name"/> が <c>null</c></exception>
        /// <exception cref="ArgumentNullException"><paramref name="src"/> が <c>null</c></exception>
        /// <exception cref="ArgumentNullException"><paramref name="e"/> が <c>null</c></exception>
        /// <exception cref="ArgumentException">指定された名前のプロパティが存在しない</exception>
        /// <seealso cref="Appearance"/>
        /// <seealso cref="Add{T}(string , T)"/>   
        public void Add<TObj, T> (string name, TObj src, Expression<Func<T>> e) where TObj : Object {
            if (name == null || name == "") {
                throw new ArgumentNullException ("Name is null or empty");
            }
            if (src == null) {
                throw new ArgumentNullException ("Source is null");
            }
            if (e == null) {
                throw new ArgumentNullException ("Expression is null");
            }
            var propName = Object.GetName (e);
            //var propInfo = src.GetType().GetProperty (propName);
            //if (propInfo == null) {
            //    throw new ArgumentException ("Can't find Property(\"" + propName + "\") at " + src);
            //}

            this.uniforms.RemoveAll (x => x.Name == name);
            this.uniforms.Add (new UniformVariable (name, src, propName));
        }

        /// <summary>
        /// <c>Uniform</c> 変数を登録します
        /// </summary>
        /// <remarks>
        /// シェーダーに <c>Uniform</c> 変数として送る即値を登録します｡
        /// 既に登録されている名前と同名の <c>Uniform</c> 変数を登録した場合は､古い方は削除され新しいものに置き換わります。
        /// 即値にはシェーダーで宣言された <c>Uniform</c> 変数の型と一致するか､互換性のある型のものを指定する必要があります｡
        /// 詳しくは <see cref="UniformVariable"/> の解説を参照してください。
        /// <note type="implement">
        /// 現在型チェックがいい加減｡
        /// 即値として指定できるのは数値型(8種)か<see cref="IExportable{T}"/>を実装した構造体だが､
        /// <see cref="Sampler"/> クラスも指定可能｡
        /// 今はノーチェックで素通しになっているので型チェックの実装はよ｡
        /// </note>
        /// </remarks>
        /// <typeparam name="T"></typeparam>
        /// <param name="name">Uniform変数名</param>
        /// <param name="src">即値</param>
        /// <exception cref="ArgumentNullException"><paramref="name"/> が <c>null</c> または空文字列</exception>
        public void Add<T> (string name, T src) {
            if (name == null || name == "") {
                throw new ArgumentNullException ("Name is null or empty");
            }
            // 型チェック抜けてる
            // 実装はよ

            this.uniforms.RemoveAll (x => x.Name == name);
            this.uniforms.Add (new UniformVariable (name, src, null));
        }


        /// <summary>
        /// <c>Uniform</c> 変数を削除します
        /// </summary>
        /// <remarks>
        /// 指定の名前の<c>Uniform</c> 変数が登録されていない場合これを無視します。
        /// </remarks>
        /// <param name="name">削除したい<c>Uniform</c> 変数の名前</param>
        /// <returns>削除したら <c>true</c>, そうでなければ <c>false</c></returns>
        public bool Remove (string name) {
            return uniforms.RemoveAll (x => x.Name == name) > 0 ? true : false;
        }

        /// <summary>
        /// 指定の名前の<c>Uniform</c> 変数を取得します
        /// </summary>
        /// <remarks>
        /// 登録済みの <c>Uniform</c> 変数の中から指定の名前の<c>Uniform</c> を取得します｡
        /// 変数が登録されていない場合は例外が発生します。
        /// </remarks>
        /// <param name="name"><c>Uniform</c> 変数名</param>
        /// <returns><c>Uniform</c> 変数をあらわす <see cref="UniformVariable"/> 構造体</returns>
        /// <seealso cref="UniformVariable"/>
        public UniformVariable Get (string name) {
            if (name == null || name == "") {
                throw new ArgumentNullException ("Name is null");
            }
            if (!(uniforms.Exists (x => x.Name == name))) {
                throw new ArgumentException("Name was not found");
            }

            return uniforms.Find (x => x.Name == name);
        }

        /// <inheritdoc/>
        public override string ToString () {
            return string.Format ("UniformArray: Count={0}", Count);
        }

        #endregion


    }
}
